import allure
from typing import Union
from playwright.sync_api import Locator, Page,  FrameLocator, Frame
from core.model import ElementModel
from settings import ELEMENT_WAIT_TIME_OUT

class ElementKeywords:
    def __init__(self, page: Page):
        self.page = page
        self.iframe = None

    def select_iframe(self, element_model: ElementModel):
        step_desc = "选择 Iframe" if element_model.desc is None else f"选择Iframe: {element_model.desc}"
        with allure.step(step_desc):
            item = self.page
            if " >> " in element_model.selector:
                parts = element_model.selector.split(" >> ")
                # 处理所有 iframe 部分
                for part in parts:
                    part = part.strip()
                    item = item.frame_locator(part)
            else:
                item = item.frame_locator(element_model.selector)
            self.iframe = item

    def clear_iframe(self):
        self.iframe = None

    def _get_locator(
            self,
            selector: str,
            index: int = 1,
            timeout: int = ELEMENT_WAIT_TIME_OUT,
    ) -> Locator:
        """
        获取元素定位器，支持 iframe 内元素定位

        :param selector: 元素选择器，支持 iframe 路径 (如 "iframe#outer >> iframe#inner >> button")
        :param index: 元素索引（当有多个匹配元素时）
        :param timeout: 等待超时时间（秒）
        :param frame_context: 起始 frame 上下文（默认为主页面）
        :return: 元素定位器
        """
        # 1. iframe存在使用iframe定位， 不存在使用page
        item = self.page if self.iframe is None else self.iframe

        base_locator = item.locator(selector)
        # 4. 等待元素可见
        try:
            base_locator.wait_for(state="visible", timeout=timeout * 1000)
        except Exception as e:
            raise TimeoutError(f"在超时时间内未找到可见元素: {selector}") from e

        # 5. 获取可见元素数量
        locator_count = base_locator.count()
        print(f"元素定位器 {selector} 成功定位可见元素数量: {locator_count}")

        # 6. 根据数量返回对应定位器
        if locator_count == 1:
            return base_locator
        elif locator_count > 1 and locator_count >= index:
            return base_locator.nth(index - 1)
        else:
            raise IndexError(
                f"元素定位器 {selector} 定位可见元素数量: {locator_count} "
                f"小于索引值: {index}"
            )
    def click(self, element_model: ElementModel, time_out = ELEMENT_WAIT_TIME_OUT):
        """
        点击元素
        :param selector: 元素选择器
        """
        step_desc = "点击元素" if element_model.desc is None else f"点击{element_model.desc}"
        with allure.step(step_desc):
            try:
                self._get_locator(selector=element_model.selector, index=element_model.index, timeout=time_out).click()
                print(f"PASS|{step_desc}成功")
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {element_model.selector}")
                raise e
                
    def input_text(self, element_model: ElementModel, text: str, time_out = ELEMENT_WAIT_TIME_OUT):
        """
        输入文本
        :param element_model: 元素模型
        :param text: 要输入的文本
        """
        step_desc = "输入文本" if element_model.desc is None else f"在{element_model.desc}输入文本"
        with allure.step(step_desc):
            try:
                self._get_locator(selector=element_model.selector, index=element_model.index, timeout=time_out).fill(text)
                print(f"PASS|{step_desc}成功")
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
                
    def drag_and_drop(self, source_model: ElementModel, target_model: ElementModel, time_out = ELEMENT_WAIT_TIME_OUT):
        """
        拖动元素
        :param source_model: 源元素模型
        :param target_model: 目标元素模型
        """
        step_desc = f"拖动{source_model.desc}到{target_model.desc}"
        with allure.step(step_desc):
            try:
                source = self._get_locator(source_model.selector, source_model.index, timeout=time_out)
                target = self._get_locator(target_model.selector, target_model.index, timeout=time_out)
                source.drag_to(target, timeout=time_out*1000)
                print(f"PASS|{step_desc}成功")
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
                
    def hover(self, element_model: ElementModel, time_out = ELEMENT_WAIT_TIME_OUT):
        """
        鼠标悬停元素
        :param element_model: 元素模型
        """
        step_desc = "鼠标悬停" if element_model.desc is None else f"鼠标悬停{element_model.desc}"
        with allure.step(step_desc):
            try:
                self._get_locator(element_model.selector, element_model.index, timeout=time_out).hover(timeout=time_out*1000)
                print(f"PASS|{step_desc}成功")
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
                
    def get_text(self, element_model: ElementModel, time_out = ELEMENT_WAIT_TIME_OUT) -> str:
        """
        获取元素文本
        :param element_model: 元素模型
        :return: 元素文本内容
        """
        step_desc = "获取文本" if element_model.desc is None else f"获取{element_model.desc}文本"
        with allure.step(step_desc):
            try:
                text = self._get_locator(element_model.selector, element_model.index, timeout=time_out).text_content(timeout=time_out*1000)
                print(f"PASS|{step_desc}成功")
                return text
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
                
    def get_attribute(self, element_model: ElementModel, attr_name: str, time_out = ELEMENT_WAIT_TIME_OUT) -> str:
        """
        获取元素属性
        :param element_model: 元素模型
        :param attr_name: 属性名
        :return: 属性值
        """
        step_desc = f"获取元素{attr_name}属性" if element_model.desc is None else f"获取{element_model.desc}的{attr_name}属性"
        with allure.step(step_desc):
            try:
                attr_value = self._get_locator(element_model.selector, element_model.index, timeout=time_out).get_attribute(attr_name, timeout=time_out*1000)
                print(f"PASS|{step_desc}成功")
                return attr_value
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
                
    def is_visible(self, element_model: ElementModel, time_out = ELEMENT_WAIT_TIME_OUT) -> bool:
        """
        判断元素是否可见
        :param element_model: 元素模型
        :return: 是否可见
        """
        step_desc = "获取元素可见性" if element_model.desc is None else f"获取{element_model.desc}可见性"
        with allure.step(step_desc):
            try:
                visible = self._get_locator(element_model.selector, element_model.index, timeout=time_out).is_visible(timeout=time_out*1000)
                print(f"PASS|{step_desc}成功: {visible}")
                return visible
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
                
    def is_enabled(self, element_model: ElementModel, time_out = ELEMENT_WAIT_TIME_OUT) -> bool:
        """
        判断元素是否可点击
        :param element_model: 元素模型
        :return: 是否可点击
        """
        step_desc = "检查元素可点击性" if element_model.desc is None else f"检查{element_model.desc}可点击性"
        with allure.step(step_desc):
            try:
                enabled = self._get_locator(element_model.selector, element_model.index, timeout=time_out).is_enabled(timeout=time_out*1000)
                print(f"PASS|{step_desc}成功: {enabled}")
                return enabled
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
                
    def wait_for_visible(self, element_model: ElementModel, time_out = ELEMENT_WAIT_TIME_OUT):
        """
        等待元素可见
        :param element_model: 元素模型
        """
        step_desc = "等待元素可见" if element_model.desc is None else f"等待{element_model.desc}可见"
        with allure.step(step_desc):
            try:
                self._get_locator(element_model.selector, element_model.index, timeout=time_out).wait_for(state="visible", timeout=time_out*1000)
                print(f"PASS|{step_desc}成功")
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
                
    def wait_for_enabled(self, element_model: ElementModel, time_out = ELEMENT_WAIT_TIME_OUT):
        """
        等待元素可点击
        :param element_model: 元素模型
        """
        step_desc = "等待元素可点击" if element_model.desc is None else f"等待{element_model.desc}可点击"
        with allure.step(step_desc):
            try:
                self._get_locator(element_model.selector, element_model.index, timeout=time_out).wait_for(state="enabled", timeout=time_out*1000)
                print(f"PASS|{step_desc}成功")
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
                
    def screenshot(self, element_model: ElementModel, path: str, time_out = ELEMENT_WAIT_TIME_OUT):
        """
        元素截图
        :param element_model: 元素模型
        :param path: 截图保存路径
        """
        step_desc = "元素截图" if element_model.desc is None else f"{element_model.desc}截图"
        with allure.step(step_desc):
            try:
                self._get_locator(element_model.selector, element_model.index, timeout=time_out).screenshot(path=path, timeout=time_out*1000)
                print(f"PASS|{step_desc}成功保存到: {path}")
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
                
    def scroll_into_view(self, element_model: ElementModel, time_out = ELEMENT_WAIT_TIME_OUT):
        """
        滚动到元素可见
        :param element_model: 元素模型
        """
        step_desc = "滚动到元素可见" if element_model.desc is None else f"滚动到{element_model.desc}可见"
        with allure.step(step_desc):
            try:
                self._get_locator(element_model.selector, element_model.index, timeout=time_out).scroll_into_view_if_needed(timeout=time_out*1000)
                print(f"PASS|{step_desc}成功")
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
                
    def right_click(self, element_model: ElementModel, time_out = ELEMENT_WAIT_TIME_OUT):
        """
        右键点击元素
        :param element_model: 元素模型
        """
        step_desc = "右键点击" if element_model.desc is None else f"右键点击{element_model.desc}"
        with allure.step(step_desc):
            try:
                self._get_locator(element_model.selector, element_model.index, timeout=time_out).click(button='right', timeout=time_out*1000)
                print(f"PASS|{step_desc}成功")
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
                
    def double_click(self, element_model: ElementModel, time_out = ELEMENT_WAIT_TIME_OUT):
        """
        双击元素
        :param element_model: 元素模型
        """
        step_desc = "双击" if element_model.desc is None else f"双击{element_model.desc}"
        with allure.step(step_desc):
            try:
                self._get_locator(element_model.selector, element_model.index, timeout=time_out).dblclick(timeout=time_out*1000)
                print(f"PASS|{step_desc}成功")
            except Exception as e:
                print(f"FAIL|{step_desc}失败: {e}")
                raise e
